#ifndef PORTABLE_BYTECODE_ARRAY_ITERATOR_H_
#define PORTABLE_BYTECODE_ARRAY_ITERATOR_H_

#include "src/pbc/portable-bytecode-decoder.h"
#include "src/pbc/portable-bytecode-register.h"
#include "src/pbc/portable-bytecodes.h"
#include "src/pbc/portable-feedback-vector.h"
#include "src/pbc/portable-format-helper.h"
#include "src/pbc/portable-primitives.h"

namespace portable {

class BytecodeArray;
class BytecodeArrayIterator;

struct JumpTableTargetOffset {
  int case_value;
  int target_offset;
};

class JumpTableTargetOffsets final {
 public:
  // Minimal iterator implementation for use in ranged-for.
  class iterator final {
   public:
    iterator(int case_value, int table_offset, int table_end,
             const BytecodeArrayIterator* iterator);

    JumpTableTargetOffset operator*();
    iterator& operator++();
    bool operator!=(const iterator& other);

   private:
    void UpdateAndAdvanceToValid();

    const BytecodeArrayIterator* iterator_;
    Smi current_;
    int index_;
    int table_offset_;
    int table_end_;
  };

  JumpTableTargetOffsets(const BytecodeArrayIterator* iterator, int table_start,
                         int table_size, int case_value_base);

  iterator begin() const;
  iterator end() const;

  int size() const;

 private:
  const BytecodeArrayIterator* iterator_;
  int table_start_;
  int table_size_;
  int case_value_base_;
};

class BytecodeArrayIterator {
 public:
  explicit BytecodeArrayIterator(BytecodeArray* bytecode_array,
                                 int initial_offset = 0);
  BytecodeArrayIterator(const uint8_t* raw_array, int length,
                        PbcConstantTable constant_pool);
  ~BytecodeArrayIterator() = default;

  BytecodeArrayIterator(const BytecodeArrayIterator&) = delete;
  BytecodeArrayIterator& operator=(const BytecodeArrayIterator&) = delete;

  inline void Advance() {
    cursor_ += current_bytecode_size_without_prefix();
    UpdateOperandScale();
  }
  void SetOffset(int offset);
  void Reset() { SetOffset(0); }

  // Whether the given offset is reachable in this bytecode array.
  static bool IsValidOffset(BytecodeArray* bytecode_array, int offset);

  inline Bytecode current_bytecode() const {
    P_DCHECK(!done());
    uint8_t current_byte = *cursor_;
    Bytecode current_bytecode = Bytecodes::FromByte(current_byte);
    P_DCHECK(!Bytecodes::IsPrefixScalingBytecode(current_bytecode));
    return current_bytecode;
  }
  int current_bytecode_size() const {
    return prefix_size_ + current_bytecode_size_without_prefix();
  }
  int current_bytecode_size_without_prefix() const {
    return Bytecodes::Size(current_bytecode(), current_operand_scale());
  }
  int current_offset() const {
    return static_cast<int>(cursor_ - start_ - prefix_size_);
  }
  uint8_t* current_address() const { return cursor_ - prefix_size_; }
  int next_offset() const { return current_offset() + current_bytecode_size(); }
  Bytecode next_bytecode() const {
    uint8_t* next_cursor = cursor_ + current_bytecode_size_without_prefix();
    if (next_cursor == end_) return Bytecode::kIllegal;
    Bytecode next_bytecode = Bytecodes::FromByte(*next_cursor);
    if (Bytecodes::IsPrefixScalingBytecode(next_bytecode)) {
      next_bytecode = Bytecodes::FromByte(*(next_cursor + 1));
    }
    return next_bytecode;
  }
  OperandScale current_operand_scale() const { return operand_scale_; }
  BytecodeArray* bytecode_array() const { return bytecode_array_; }

  uint32_t GetFlag8Operand(int operand_index) const {
    P_DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
                OperandType::kFlag8);
    return GetUnsignedOperand(operand_index, OperandType::kFlag8);
  }
  uint32_t GetFlag16Operand(int operand_index) const {
    P_DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
                OperandType::kFlag16);
    return GetUnsignedOperand(operand_index, OperandType::kFlag16);
  }
  uint32_t GetUnsignedImmediateOperand(int operand_index) const {
    P_DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
                OperandType::kUImm);
    return GetUnsignedOperand(operand_index, OperandType::kUImm);
  }
  int32_t GetImmediateOperand(int operand_index) const {
    P_DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
                OperandType::kImm);
    return GetSignedOperand(operand_index, OperandType::kImm);
  }
  uint32_t GetIndexOperand(int operand_index) const {
    OperandType operand_type =
        Bytecodes::GetOperandType(current_bytecode(), operand_index);
    P_DCHECK_EQ(operand_type, OperandType::kIdx);
    return GetUnsignedOperand(operand_index, operand_type);
  }
  Register GetParameter(int parameter_index) const {
    P_DCHECK_GE(parameter_index, 0);
    // The parameter indices are shifted by 1 (receiver is the
    // first entry).
    return Register::FromParameterIndex(parameter_index + 1);
  }
  uint32_t GetRegisterCountOperand(int operand_index) const {
    P_DCHECK_EQ(Bytecodes::GetOperandType(current_bytecode(), operand_index),
                OperandType::kRegCount);
    return GetUnsignedOperand(operand_index, OperandType::kRegCount);
  }
  Register GetRegisterOperand(int operand_index) const {
    OperandType operand_type =
        Bytecodes::GetOperandType(current_bytecode(), operand_index);
    Address operand_start =
        reinterpret_cast<Address>(cursor_) +
        Bytecodes::GetOperandOffset(current_bytecode(), operand_index,
                                    current_operand_scale());
#if V8_MAJOR_VERSION == 11
    Register pbc_register = BytecodeDecoder::DecodeRegisterOperand(
        operand_start, operand_type, current_operand_scale());
    return Register::ToV8Register(pbc_register);
#else
    return BytecodeDecoder::DecodeRegisterOperand(operand_start, operand_type,
                                                  current_operand_scale());
#endif
  }
  Register GetStarTargetRegister() const {
    Bytecode bytecode = current_bytecode();
    P_DCHECK(Bytecodes::IsAnyStar(bytecode));
    if (Bytecodes::IsShortStar(bytecode)) {
      return Register::FromShortStar(bytecode);
    } else {
      P_DCHECK_EQ(bytecode, Bytecode::kStar);
      P_DCHECK_EQ(Bytecodes::NumberOfOperands(bytecode), 1);
      P_DCHECK_EQ(Bytecodes::GetOperandTypes(bytecode)[0],
                  OperandType::kRegOut);
      return GetRegisterOperand(0);
    }
  }
  std::pair<Register, Register> GetRegisterPairOperand(
      int operand_index) const {
    Register first = GetRegisterOperand(operand_index);
    Register second(first.index() + 1);
    return std::make_pair(first, second);
  }
  RegisterList GetRegisterListOperand(int operand_index) const {
    Register first = GetRegisterOperand(operand_index);
    uint32_t count = GetRegisterCountOperand(operand_index + 1);
    return RegisterList(first.index(), count);
  }
  int GetRegisterOperandRange(int operand_index) const {
    P_DCHECK_LE(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
    const OperandType* operand_types =
        Bytecodes::GetOperandTypes(current_bytecode());
    OperandType operand_type = operand_types[operand_index];
    P_DCHECK(Bytecodes::IsRegisterOperandType(operand_type));
    if (operand_type == OperandType::kRegList ||
        operand_type == OperandType::kRegOutList) {
      return GetRegisterCountOperand(operand_index + 1);
    } else {
      return Bytecodes::GetNumberOfRegistersRepresentedBy(operand_type);
    }
  }
  Runtime::FunctionId GetRuntimeIdOperand(int operand_index) const {
    OperandType operand_type =
        Bytecodes::GetOperandType(current_bytecode(), operand_index);
    P_DCHECK_EQ(operand_type, OperandType::kRuntimeId);
    uint32_t raw_id = GetUnsignedOperand(operand_index, operand_type);
    return static_cast<Runtime::FunctionId>(raw_id);
  }
  Runtime::FunctionId GetIntrinsicIdOperand(int operand_index) const {
    OperandType operand_type =
        Bytecodes::GetOperandType(current_bytecode(), operand_index);
    P_DCHECK_EQ(operand_type, OperandType::kIntrinsicId);
    uint32_t raw_id = GetUnsignedOperand(operand_index, operand_type);
    return Runtime::ToRuntimeFunctionId(raw_id);
  }
  uint32_t GetNativeContextIndexOperand(int operand_index) const {
    OperandType operand_type =
        Bytecodes::GetOperandType(current_bytecode(), operand_index);
    P_DCHECK_EQ(operand_type, OperandType::kNativeContextIndex);
    return GetUnsignedOperand(operand_index, operand_type);
  }
  bool IsConstantAtIndexSmi(int offset) const;
  Smi GetConstantAtIndexAsSmi(int offset) const;

  // Returns the relative offset of the branch target at the current bytecode.
  // It is an error to call this method if the bytecode is not for a jump or
  // conditional jump. Returns a negative offset for backward jumps.
  int GetRelativeJumpTargetOffset() const;
  // Returns the absolute offset of the branch target at the current bytecode.
  // It is an error to call this method if the bytecode is not for a jump or
  // conditional jump.
  int GetJumpTargetOffset() const;
  // Returns an iterator over the absolute offsets of the targets of the current
  // switch bytecode's jump table. It is an error to call this method if the
  // bytecode is not a switch.
  JumpTableTargetOffsets GetJumpTableTargetOffsets() const;

  // Returns the absolute offset of the bytecode at the given relative offset
  // from the current bytecode.
  int GetAbsoluteOffset(int relative_offset) const;

  int GetOffsetWithPrefix(int relative_offset) const;

  std::ostream& PrintTo(std::ostream& os) const;

  static void UpdatePointersCallback(void* iterator) {
    reinterpret_cast<BytecodeArrayIterator*>(iterator)->UpdatePointers();
  }

  void UpdatePointers();

  inline bool done() const { return cursor_ >= end_; }

  bool operator==(const BytecodeArrayIterator& other) const {
    return cursor_ == other.cursor_;
  }
  bool operator!=(const BytecodeArrayIterator& other) const {
    return cursor_ != other.cursor_;
  }

 private:
  uint32_t GetUnsignedOperand(int operand_index,
                              OperandType operand_type) const {
    P_DCHECK_GE(operand_index, 0);
    P_DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
    P_DCHECK_EQ(operand_type,
                Bytecodes::GetOperandType(current_bytecode(), operand_index));
    P_DCHECK(Bytecodes::IsUnsignedOperandType(operand_type));
    Address operand_start =
        reinterpret_cast<Address>(cursor_) +
        Bytecodes::GetOperandOffset(current_bytecode(), operand_index,
                                    current_operand_scale());
    return BytecodeDecoder::DecodeUnsignedOperand(operand_start, operand_type,
                                                  current_operand_scale());
  }
  int32_t GetSignedOperand(int operand_index, OperandType operand_type) const {
    P_DCHECK_GE(operand_index, 0);
    P_DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(current_bytecode()));
    P_DCHECK_EQ(operand_type,
                Bytecodes::GetOperandType(current_bytecode(), operand_index));
    P_DCHECK(!Bytecodes::IsUnsignedOperandType(operand_type));
    Address operand_start =
        reinterpret_cast<Address>(cursor_) +
        Bytecodes::GetOperandOffset(current_bytecode(), operand_index,
                                    current_operand_scale());
    return BytecodeDecoder::DecodeSignedOperand(operand_start, operand_type,
                                                current_operand_scale());
  }

  inline void UpdateOperandScale() {
    if (done()) return;
    uint8_t current_byte = *cursor_;
    Bytecode current_bytecode = Bytecodes::FromByte(current_byte);
    if (Bytecodes::IsPrefixScalingBytecode(current_bytecode)) {
      operand_scale_ =
          Bytecodes::PrefixBytecodeToOperandScale(current_bytecode);
      ++cursor_;
      prefix_size_ = 1;
    } else {
      operand_scale_ = OperandScale::kSingle;
      prefix_size_ = 0;
    }
  }

  BytecodeArray* bytecode_array_;
  PbcConstantTable constant_pool_;
  uint8_t* start_;
  uint8_t* end_;
  // The cursor always points to the active bytecode. If there's a prefix, the
  // prefix is at (cursor - 1).
  uint8_t* cursor_;
  OperandScale operand_scale_;
  int prefix_size_;
};

}  // namespace portable

#endif  // PORTABLE_BYTECODE_ARRAY_ITERATOR_H_
